package it.nm.unipicontrol.gateway;

import it.nm.unipicontrol.gateway.event.UniPiEventListener;
import it.nm.unipicontrol.gateway.event.UniPiRelayEvent;
import it.nm.unipicontrol.gateway.event.UniPiRelayEventListener;
import it.nm.unipicontrol.model.UniPIData;
import it.nm.unipicontrol.model.UniPIResponse;
import it.nm.unipicontrol.model.UniPISensor;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

public class UniPIGateway {

	// Constants to hold bit masks for desired flags
	static final int flagAllOff = 0; // 000...00000000 (empty mask)

	// private static final String GET_RELAY_1 = "/rest/relay/1";

	// private static final String SET_RELAY_1 = "/rest/relay/1";

	static final int flagbit1 = 1; // 2^^0 000...00000001

	static final int flagbit2 = 2; // 2^^1 000...00000010

	static final int flagbit3 = 4; // 2^^2 000...00000100
	static final int flagbit4 = 8; // 2^^3 000...00001000

	static final int flagbit5 = 16; // 2^^4 000...00010000

	static final int flagbit6 = 32; // 2^^5 000...00100000

	static final int flagbit7 = 64; // 2^^6 000...01000000

	static final int flagbit8 = 128; // 2^^7 000...10000000

	private static final String GET_ALL = "/rest/all";

	public final static UniPIGateway INSTANCE = new UniPIGateway();

	private static final Logger logger = Logger.getLogger(UniPIGateway.class
			.getName());

	private static final String UNIPI_URL_KEY = "unipi.url";

	private List<UniPiEventListener> _listeners = new ArrayList<UniPiEventListener>();

	private String UNIPI_URL;

	private final String USER_AGENT = "Mozilla/5.0";

	private UniPIGateway() {
		try {
			readConfigFile();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public synchronized void addEventListener(UniPiEventListener listener) {
		_listeners.add(listener);
	}

	public void connect() throws Exception {
		getState();
	}
	private synchronized void fireRelayEvent(int relay, int status) {
		UniPiRelayEvent event = new UniPiRelayEvent(this, relay, status);
		Iterator<UniPiEventListener> i = _listeners.iterator();
		while (i.hasNext()) {
			UniPiEventListener l = i.next();
			if (l instanceof UniPiRelayEventListener) {
				((UniPiRelayEventListener) l).handleUniPiClassEvent(event);
			}
			
		}
	}
	public boolean getRelay(int relay) throws Exception {

		String sensor = "";

		switch (relay) {
		case 1:
			sensor = UniPISensor.RELAY_1;
			break;
		case 2:
			sensor = UniPISensor.RELAY_2;
			break;
		case 3:
			sensor = UniPISensor.RELAY_3;
			break;
		case 4:
			sensor = UniPISensor.RELAY_4;
			break;
		case 5:
			sensor = UniPISensor.RELAY_5;
			break;
		case 6:
			sensor = UniPISensor.RELAY_6;
			break;
		case 7:
			sensor = UniPISensor.RELAY_7;
			break;
		case 8:
			sensor = UniPISensor.RELAY_8;
			break;
		}

		if (sensor.equals(""))
			throw new Exception("bad syntax: relay not existing");

		UniPIResponse resp = parseUniPIResponse(sendCommand(sensor + "/value"));

		boolean ret = resp.getValue().equals("1");

		return ret;

	}
	/**
	 * parse state and throws events
	 * @return
	 * @throws Exception
	 */
	public ArrayList<UniPIData> getState() throws Exception {
		ArrayList<UniPIData> ret = parseUniPIData(sendCommand(GET_ALL));
		
		for (UniPIData d : ret) {
			if (d.getDev().equals("relay")) {
				fireRelayEvent(Integer.parseInt(d.getCircuit()), d.getValue().equals("1")?1:0);
			}
		}

		
		return ret;
	}
	private Map<String, String> parseMap(String s) {

		Gson gson = new Gson();

		BufferedReader br = new BufferedReader(new StringReader(s));

		Type collectionType = new TypeToken<ArrayList<UniPIData>>() {
		}.getType();

		Map<String, String> data = new HashMap<String, String>();

		data = gson.fromJson(br, collectionType);

		logger.log(Level.FINE, "deserialized: " + data.toString());

		return data;

	}
	private ArrayList<UniPIData> parseUniPIData(String s) {

		Gson gson = new Gson();

		BufferedReader br = new BufferedReader(new StringReader(s));

		Type collectionType = new TypeToken<ArrayList<UniPIData>>() {
		}.getType();

		ArrayList<UniPIData> data = new ArrayList<UniPIData>();

		data = gson.fromJson(br, collectionType);

		logger.log(Level.FINE, "deserialized: " + data.toString());

		return data;

	}
	private UniPIResponse parseUniPIResponse(String s) {

		Gson gson = new Gson();

		BufferedReader br = new BufferedReader(new StringReader(s));

		// Type collectionType = new
		// TypeToken<ArrayList<UniPIData>>(){}.getType();

		UniPIResponse data = new UniPIResponse();

		data = gson.fromJson(br, UniPIResponse.class);

		logger.log(Level.FINE, "deserialized: " + data.toString());

		return data;

	}
	private void readConfigFile() throws Exception {

		URL url = this.getClass().getResource("unipicontrol.properties");
		logger.log(Level.FINE, "reading configuration file: " + url.getPath());
		Properties p = new Properties();
		InputStream s = new FileInputStream(url.getPath());
		if (s != null) {
			p.load(s);
		}
		UNIPI_URL = p.getProperty(UNIPI_URL_KEY);

	}
	public synchronized void removeEventListener(UniPiEventListener listener) {
		_listeners.remove(listener);
	}

	private String sendCommand(String command) throws Exception {
		return sendCommand(command, new HashMap<String, String>());
	}

	// private String getSensorState(String sensor) throws Exception {
	//
	// return sendCommand(sensor+"/value");
	// }

	/**
	 * abstraction for GET and POST commands
	 * 
	 * @param command
	 * @param value
	 * @return
	 * @throws Exception
	 */
	private String sendCommand(String command, Map<String, String> values)
			throws Exception {

		if (UniPISensor.isRelay(command) && !values.isEmpty()) {
			return sendPostCommand(command, values);
		}

		logger.log(Level.FINE, "Sending command: " + command);
		String url = UNIPI_URL.concat(command);

		URL obj = new URL(url);
		HttpURLConnection con = (HttpURLConnection) obj.openConnection();

		// optional default is GET
		con.setRequestMethod("GET");
		con.setReadTimeout(1000);

		// add request header
		con.setRequestProperty("User-Agent", USER_AGENT);

		int responseCode = con.getResponseCode();

		logger.log(Level.FINE, "Sending 'GET' request to URL : " + url);
		logger.log(Level.FINE, "Response Code : " + responseCode);

		if (responseCode != 200) {
			throw new Exception("UniPI bad response code: " + responseCode
					+ ". Aborting communication");
		}

		BufferedReader in = new BufferedReader(new InputStreamReader(
				con.getInputStream()));
		String inputLine;
		StringBuffer response = new StringBuffer();

		while ((inputLine = in.readLine()) != null) {
			response.append(inputLine);
		}
		in.close();

		// print result
		logger.log(Level.FINE, "received: " + response.toString());

		return response.toString();

	}

	private String sendPostCommand(String command, Map<String, String> values)
			throws Exception {

		logger.log(Level.FINE, "Sending command: " + command);
		String url = UNIPI_URL.concat(command);
		URL obj = new URL(url);
		HttpURLConnection con = (HttpURLConnection) obj.openConnection();

		// add reuqest header
		con.setRequestMethod("POST");
		con.setConnectTimeout(1000);
		con.setRequestProperty("User-Agent", USER_AGENT);
		con.setRequestProperty("Accept-Language", "en-US,en;q=0.5");

		String urlParameters = "";// " --post-data='";

		for (String key : values.keySet()) {
			urlParameters = urlParameters + (key + "=" + values.get(key));
			logger.log(Level.FINE, "adding property: " + key + " with value '"
					+ values.get(key) + "'");
		}
		// urlParameters=urlParameters+"'";

		// Send post request
		con.setDoOutput(true);
		DataOutputStream wr = new DataOutputStream(con.getOutputStream());
		wr.writeBytes(urlParameters);
		wr.flush();
		wr.close();

		int responseCode = con.getResponseCode();
		logger.log(Level.FINE, "\nSending 'POST' request to URL : " + url);
		logger.log(Level.FINE, "Post parameters : " + urlParameters);
		logger.log(Level.FINE, "Response Code : " + responseCode);

		if (responseCode != 200) {
			throw new Exception("UniPI bad response code: " + responseCode
					+ ". Aborting communication");
		}

		BufferedReader in = new BufferedReader(new InputStreamReader(
				con.getInputStream()));
		String inputLine;
		StringBuffer response = new StringBuffer();

		while ((inputLine = in.readLine()) != null) {
			response.append(inputLine);
		}
		in.close();

		// print result
		logger.log(Level.FINE, "received: " + response.toString());
		return response.toString();

	}

	/**
	 * 
	 * @param relay
	 *            the number of the relay (1-8)
	 * @param state
	 *            true = 1; false = 0
	 * @return
	 * @throws Exception
	 */
	public boolean setRelay(int relay, boolean state) throws Exception {

		String sensor = "";

		switch (relay) {
		case 1:
			sensor = UniPISensor.RELAY_1;
			break;
		case 2:
			sensor = UniPISensor.RELAY_2;
			break;
		case 3:
			sensor = UniPISensor.RELAY_3;
			break;
		case 4:
			sensor = UniPISensor.RELAY_4;
			break;
		case 5:
			sensor = UniPISensor.RELAY_5;
			break;
		case 6:
			sensor = UniPISensor.RELAY_6;
			break;
		case 7:
			sensor = UniPISensor.RELAY_7;
			break;
		case 8:
			sensor = UniPISensor.RELAY_8;
			break;
		}

		if (sensor.equals(""))
			throw new Exception("bad syntax: relay not existing");

		UniPIResponse ret = parseUniPIResponse(setSensorState(sensor,
				state ? "1" : "0"));

		if (!ret.getSuccess().equals(UniPISensor.PROT_TRUE))
			throw new Exception("operation failed");
		else
			fireRelayEvent(relay, -1);
		
		fireRelayEvent(relay, ret.getResult().equals("1")?0:1);

		return ret.getResult().equals("1");
	}

	public int setRelayMask(int mask) throws Exception {

		int ret = 0;

		ret = (int) (ret + ((setRelay(1, (mask & flagbit1) == 1) ? true : false) ? 1
				: 0));
		ret = (int) (ret + Math
				.pow((setRelay(2, ((mask & flagbit2) == flagbit2 ? 1 : 0) == 1) ? true
						: false) ? 2 : 0, 1));
		ret = (int) (ret + Math
				.pow((setRelay(3, ((mask & flagbit3) == flagbit3 ? 1 : 0) == 1) ? true
						: false) ? 2 : 0, 2));
		ret = (int) (ret + Math
				.pow((setRelay(4, ((mask & flagbit4) == flagbit4 ? 1 : 0) == 1) ? true
						: false) ? 2 : 0, 3));
		ret = (int) (ret + Math
				.pow((setRelay(5, ((mask & flagbit5) == flagbit5 ? 1 : 0) == 1) ? true
						: false) ? 2 : 0, 4));
		ret = (int) (ret + Math
				.pow((setRelay(6, ((mask & flagbit6) == flagbit6 ? 1 : 0) == 1) ? true
						: false) ? 2 : 0, 5));
		ret = (int) (ret + Math
				.pow((setRelay(7, ((mask & flagbit7) == flagbit7 ? 1 : 0) == 1) ? true
						: false) ? 2 : 0, 6));
		ret = (int) (ret + Math
				.pow((setRelay(8, ((mask & flagbit8) == flagbit8 ? 1 : 0) == 1) ? true
						: false) ? 2 : 0, 7));

		return ret;
	}

	private String setSensorState(String sensor, String state) throws Exception {

		HashMap<String, String> map = new HashMap<String, String>();
		map.put("value", state);
		return sendCommand(sensor, map);

	}

}
